#include <stdlib.h>
#include <string.h>
#include <directfb.h>

#include "dtk_types.h"
#include "dtk_debug.h"
#include "dtk_priv.h"
#include "dtk_actor.h"
#include "dtk_text.h"

const DString DTK_TYPE_TEXT = "DTKText";

void dtk_text_set_line_mode (DTKActor* self, DBoolean single_line);

static void text_draw(DTKActor* self, DTKGC* gc, DRegion* region)
{
    DTKText* text = (DTKText*) self;
    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);
    DInt x, y;

    dtk_region_dump (__func__, region);

    if (text->string == NULL)
    {
        return;
    }


    gc->surface->SetColor (gc->surface,
                    text->color.red,
                    text->color.green,
                    text->color.blue,
                    text->color.alpha);

    if (text->font == NULL)
    {
        return;
    }

    gc->surface->SetFont (gc->surface, text->font);

    if (text->single_line)
    {
        gc->surface->DrawString (gc->surface,
            text->string, -1,
            region->x1, region->y1,
            DSTF_TOPLEFT);
    }
    else
    {
        DInt length = text->length;
        DString pos = text->string;
        DInt max_width;
        DInt ret_str_length;
        DInt ret_width;
        DString ret_next_line;
        DInt h = 0, font_height;

        if (DTK_FALSE == dtk_actor_get_size(self, &max_width, NULL))
        {
            max_width = region->x2 - region->x1;
        }

        text->font->GetHeight (text->font, &font_height);
        while (length)
        {
            text->font->GetStringBreak (text->font,
                        pos,
                        length,
                        max_width,
                        &ret_width,
                        &ret_str_length,
                        &ret_next_line);
            
            gc->surface->DrawString (gc->surface, pos, ret_str_length,
                region->x1, h + region->y1, DSTF_TOPLEFT);
            length -= ret_str_length;
            pos = ret_next_line;
            h += font_height;
        }
    }

    dtk_actor_release_surface (self);
    return;
}

void text_release (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    if (text->font)
    {
        text->font->Release (self);
    }

    if (text->font_name)
    {
        free (text->font_name);
    }

    if (text->string)
    {
        free (text->string);
    }
  
    return;
}

static void text_update (DTKLayout* self)
{
    DTKText* text;
    DTKActor* actor;
    if (self->caps != LOCAPS_FIXSIZE)
    {
        DUInt width, height;
        actor = dtk_layout_get_actor(self);
        
        DTK_TYPE_ASSERT(actor, DTK_TYPE_TEXT);
        text = (DTKText*)actor;

        if (text->string == NULL
            && text->font == NULL
            && text->single_line != DTK_TRUE)
        {
            return;
        }

        text->font->GetHeight(text->font, &height);
        text->font->GetStringWidth(text->font,
                                    text->string,
                                    text->length,
                                    &width);
        dtk_layout_set_min_size(self, width, height);
    }
    return;
}

DTKActor* dtk_text_new (void)
{
    DTKActor*               actor;
    DTKActorCallback*       funcs;
    DTKLayout*              layout;
    DColor                color = {0x00, 0x00, 0x00, 0xff};

    actor = dtk_actor_init (sizeof(DTKText), DTK_TYPE_TEXT, DACAPS_NONE);

    dtk_text_set_color (actor, &color);
    dtk_text_set_line_mode(actor, DTK_TRUE);
    layout = dtk_actor_get_layout (actor);
    
    funcs = DTK_ACTOR_CALLBACK(actor);

    layout->update = text_update;

    funcs->draw = text_draw;
    funcs->release = text_release;

    return actor;
}

DBoolean dtk_text_set_text(DTKActor* self, DString string)
{
    DTKText* text = (DTKText*) self;
    DFBResult ret;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    if (text->string)
    {
        free (text->string);
        text->string = NULL;
        text->length = 0;
    }

    if (string == NULL)
    {
        return DTK_FALSE;
    }

    text->length = strlen (string);
    if (text->max_length > 0 && text->length > text->max_length)
    {
        text->length = text->max_length;
    }

    text->string = strndup (string, text->length + 1);    

    return DTK_TRUE;
}

void dtk_text_set_font_name (DTKActor* self, DString font_name, DInt size)
{
    DTKText* text = (DTKText*) self;
    DFBFontDescription desc;
    IDirectFB* dfb;
    DFBResult ret;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    if (text->font_name)
    {
        free (text->font_name);
        text->font_name = NULL;
    }

    if (text->font)
    {
        text->font->Release (text->font);
        text->font = NULL;
    }

    if (font_name == NULL)
    {
        return;
    }

    text->font_name = strdup (font_name);

    desc.flags = DFDESC_HEIGHT|DFDESC_ATTRIBUTES;
    desc.attributes = DFFA_NONE;
    desc.height = size;
    dfb = dtk_get_dfb();
    ret = dfb->CreateFont (dfb, text->font_name, &desc, &text->font);
    if (DFB_OK != ret)
    {
        return;
    }
    
    return;
}

DString dtk_text_get_font_name (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->font_name;
}

void dtk_text_set_color (DTKActor* self, DColor* color)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->color = *color;

    return;
}

void dtk_text_get_color (DTKActor* self, DColor *color)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);
    if (NULL == color)
    {
        return;
    }

    *color = text->color;

    return;
}

DTKActor* dtk_text_new_with_text (DString font_name, DString string, DInt size)
{
    DTKText* text;
    DTKActor* actor;

    text = dtk_text_new();
    actor = DTK_ACTOR(text);
    dtk_text_set_font_name (actor, font_name, size);
    dtk_text_set_text (actor, string);
    return actor;
}

DString dtk_text_get_text (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->string;
}

void dtk_text_set_activatable (DTKActor *self, DBoolean activatable)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->activatable = activatable;
    return;
}

DBoolean dtk_text_get_activatable (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->activatable;
}

void dtk_text_set_password_char (DTKActor* self, DUniChar wc)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->password_char = wc;

    return;
}

DUniChar dtk_text_get_password_char (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->password_char;
}

void dtk_text_set_line_wrap (DTKActor* self, DBoolean line_wrap)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->line_wrap = line_wrap;

    return;
}

DBoolean dtk_text_get_line_wrap(DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->line_wrap;
}

void dtk_text_set_max_length (DTKActor* self, DInt max)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->max_length = max;

    return;
}

DInt dtk_text_get_max_length (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->max_length;
}

void dtk_text_set_selectable (DTKActor* self, DBoolean selectable)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->selectable = selectable;

    return;
}

DBoolean dtk_text_get_selectable (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->selectable;
}

void dtk_text_set_selection (DTKActor* self, DInt start_pos, DInt end_pos)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    if (start_pos > text->max_length || start_pos > strlen(text->string))
    {
        return;
    }

    text->selected.start_pos = (start_pos < 0) ? 0 : start_pos;

    text->selected.end_pos = (end_pos < 0) ? 
        strlen (text->string) : 
        (end_pos > strlen (text->string)) ? strlen(text->string) : end_pos;

    return;
}

DString dtk_text_get_selection (DTKActor* self)
{
    DString string;
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return strndup (text->string + text->selected.start_pos,
        text->selected.end_pos - text->selected.start_pos);
}

void dtk_text_set_line_mode (DTKActor* self, DBoolean single_line)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->single_line = single_line;
    return;
}

DBoolean dtk_text_get_line_mode (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->single_line;
}

void dtk_text_set_editable (DTKActor* self, DBoolean editable)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    text->editable = editable;

    return;
}

DBoolean dtk_text_get_editable (DTKActor* self)
{
    DTKText* text = (DTKText*) self;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    return text->editable;
}

void dtk_text_insert_text (DTKActor* self, DString string, DInt position)
{
    DInt insert_length, length;
    DTKText* text = (DTKText*) self;
    DString new_string;

    DTK_TYPE_ASSERT (self, DTK_TYPE_TEXT);

    length = strlen (text->string);
    insert_length = strlen (string);

    if (text->max_length > 0 && (length + insert_length > text->max_length))
    {
        return;
    }

    new_string = (DString) malloc (length + insert_length + 1);

    memcpy (new_string, text->string, position);
    memcpy (new_string + position, string, insert_length);
    memcpy (new_string + position + insert_length,
        text->string + position, length - position);

    new_string[length + insert_length] = 0;

    dtk_text_set_text(self, new_string);

    free (new_string);
    return;
}